package org.jmetrix.jmx;

import org.jmetrix.api.Aggregator;
import org.jmetrix.expose.Exposer;
import org.jmetrix.group.GroupAggregationTree;

import javax.management.*;
import java.util.*;

/**
 * @author Konstantin Kuzmin
 * @version $Revision$
 * @since 05.10.11
 */
public class JmxExposer implements Exposer<GroupAggregationTree> {
    public static final String DEFAULT_DOMAIN = "org.jmetrix";
    private String domain = DEFAULT_DOMAIN;
    private MBeanServer mBeanServer;
    private final List<ObjectName> registered = new LinkedList<ObjectName>();

    @Override
    public void expose(final GroupAggregationTree aggregationTree) {
        exposeRecursively(aggregationTree, new String[]{});
    }

    protected void exposeRecursively(final GroupAggregationTree aggregationTree, final String[] groups) {
        for (final Aggregator aggregator : aggregationTree.getAggregators()) {
            expose(aggregator, groups);
        }
        // Copy to avoid the parallel modification // TODO check
        final Set<Map.Entry<String, GroupAggregationTree>> subTreeEntries = new HashSet<Map.Entry<String, GroupAggregationTree>>(aggregationTree.getSubTree().entrySet());
        for (final Map.Entry<String, GroupAggregationTree> subTreeEntry : subTreeEntries) {
            final GroupAggregationTree subTree = subTreeEntry.getValue();
            final String[] nextGroups = Arrays.copyOf(groups, groups.length + 1);
            nextGroups[groups.length] = subTreeEntry.getKey();
            exposeRecursively(subTree, nextGroups);
        }
    }

    protected void expose(final Aggregator aggregator, final String[] groups) {
        try {
            final String objectNameString = getObjectName(groups);
            final ObjectName objectName = new ObjectName(objectNameString);
            final MBeanServer mbeanServer = getMBeanServer();
            synchronized (this) {
                if (!mbeanServer.isRegistered(objectName)) {
                    registerMBean(aggregator, objectName);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected String getObjectName(final String[] groups) {
        if (groups.length == 0) {
            return getDomain() + ':';
        } else {
            final StringBuilder objectName = new StringBuilder(getDomain() + ':');
            for (int i = 0; i < groups.length; i++) {
                objectName.append("group").append(i).append('=').append(groups[i]).append(',');
            }
            objectName.append("name=").append(groups[groups.length - 1]);
            return objectName.toString();
        }
    }

    protected void registerMBean(final Aggregator aggregator, final ObjectName objectName)
            throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
        final Object mBean = createMBeanInstance(aggregator);
        if (mBean != null) {
            getMBeanServer().registerMBean(mBean, objectName);
            synchronized (registered) {
                registered.add(objectName);
            }
        }
    }

    protected Object createMBeanInstance(final Aggregator aggregator) {
        return aggregator;
    }

    public void unregisterMBeans() {
        synchronized (registered) {
            while (!registered.isEmpty()) {
                final ObjectName objectName = registered.get(0);
                if (getMBeanServer().isRegistered(objectName)) {
                    try {
                        getMBeanServer().unregisterMBean(objectName);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
                registered.remove(0);
            }
        }
    }

    protected MBeanServer getMBeanServer() {
        return mBeanServer;
    }

    public void setMBeanServer(final MBeanServer mBeanServer) {
        this.mBeanServer = mBeanServer;
    }

    public void setDomain(String domain) {
        this.domain = domain;
    }

    public String getDomain() {
        return domain;
    }
}